package uk.ac.cam.djjb2.mygame;

import java.io.DataOutput;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public class TileMapProxy extends ReplicaBase implements TileMapObserver, ServerComponent {
	private class ClientData {
		private Set<Vector2i> prevVisibleTiles = Collections.unmodifiableSet(new HashSet<Vector2i>());
	}
	
	private final TileMap target;
	private HashSet<Vector2i> changedTiles = new HashSet<Vector2i>();
	private HashMap<Integer, ClientData> clients = new HashMap<Integer, ClientData>();

	public TileMapProxy(ReplicaManager manager, TileMap target) throws ReplicationException {
		super(manager);
		this.target = target;
		this.target.addObserver(this);
		
		for (Client client : this.getReplicaManager().getNetManager().getClients())
			clients.put(client.getId(), new ClientData());
		this.getReplicaManager().getNetManager().addComponent(this);
	}
	
	@Override
	public void destroy() {
		super.destroy();
		
		this.target.removeObserver(this);
	}
	
	@Override
	public Class<? extends ClientReplica> getClientClass() {
		return ClientTileMapProxy.class;
	}

	public TileMap getTarget() {
		return target;
	}
	
	@Override
	public void sendCreateMessage(Client client, DataOutput message)
			throws IOException {

		Vector2i size = target.getSize();
		
		Vector2i.write(message, size);
		
		super.sendCreateMessage(client, message);
	}
	
	@Override
	public boolean postUpdate() {
		changedTiles.clear();
		
		return super.postUpdate();
	}

	@Override
	public void sendUpdateMessage(Client client, DataOutput message)
			throws IOException {
		super.sendUpdateMessage(client, message);
		
		ClientData data = clients.get(client.getId());
		
		Player player = client.getPlayer();
		
		if (player != null) {
			Set<Vector2i> visibleTiles = player.getVisibleTiles();
			HashSet<Vector2i> tiles = new HashSet<Vector2i>(visibleTiles);
			tiles.removeAll(data.prevVisibleTiles);
			tiles.addAll(changedTiles);
			tiles.retainAll(visibleTiles);
			data.prevVisibleTiles = visibleTiles;
			
			message.writeInt(tiles.size());
			for (Vector2i tile : tiles) {
				Vector2i.write(message, tile);
				message.writeInt(target.getTileId(tile));
			}
		} else {
			message.writeInt(0);
		}
	}

	@Override
	public void tileChanged(Vector2i pos, int oldTileId, int newTileId) {
		changedTiles.add(pos);
	}

	@Override
	public void clientConnected(Client client) {
		clients.put(client.getId(), new ClientData());
	}

	@Override
	public void clientDisconnected(Client client) {
		clients.remove(client.getId());
	}
}
